﻿///<reference path='.\compiler\typescript.ts' />
///<reference path='typescript-node-definitions/node.d.ts'/>

module QA{
    export class TsCodeAnalyzer{

        /**
         *  TypeScript Parser
         */
        static parser: TypeScript.Parser = new TypeScript.Parser();

        static debugMode : Boolean = true;
	
        /**
         *  Entry point. Starts analyzing a code.
         *  
         */
        static analyze(code: TypeScript.StringSourceText, fileName:string): void {
            var a = 0,b=1;
            console.log("Parsing : [" + code.getLength() + "]");

            var ret:TypeScript.Script = TsCodeAnalyzer.parser.parse(code, fileName, 0);
            
            var errorList:string[] = [];
            TsCodeAnalyzer.analyzeScript(ret, errorList);

            TsCodeAnalyzer.outputErrors(errorList);
        }

        static debug(txt: string, indent: number = 0): void {
            if (TsCodeAnalyzer.debugMode) {
                var ss = "";
                for (var i: number = 0; i < indent; i++){
                    ss += "    ";
                }
                console.log(ss + txt);
            }
        }

        static outputErrors(error: string[]):void {
            console.log("Errors detected : [" + error.length + "]");
            for (var i = 0; i < error.length; i++){
                console.log(error[i]);
            }
        }

        /**
         *      Analyze and Identifier
         *      Currently checks the id length and if it starts with "_"
         */
        static analyzeIdentifier(id: TypeScript.Identifier, errors: string[], indent: number = 0): void {

            TsCodeAnalyzer.debug("Analyzing Identifier",indent);

            var lineCol = { line: -1, col: -1 };
            TsCodeAnalyzer.parser.getSourceLineCol(lineCol, id.minChar);
            
            if (id.actualText.length < 3) {
                errors.push("Identifier too short : (" + lineCol.line + "," + lineCol.col + ") : "
                    + id.printLabel());
            }
            if (id.actualText === null
                || (id.actualText.length > 0 && id.actualText[0] === '_' )) {
                errors.push("Dangling char '_' in identifier : (" + lineCol.line + "," + lineCol.col + ") : "
                    + id.printLabel());
            }
            
        }

        /**
         *  Analyzes a Var declaration
         */
        static analyzeVarDecl(variable: TypeScript.VarDecl, errors: string[], indent: number = 0): void {
            TsCodeAnalyzer.debug("Analyzing VarDecl", indent);

            TsCodeAnalyzer.analyzeIdentifier(variable.id, errors, indent+1);
        }

        static analyzeClassDeclaration(classObj: TypeScript.ClassDeclaration, errors: string[], indent: number = 0): void {
            TsCodeAnalyzer.debug("Analyzing ClassDeclaration", indent);

            TsCodeAnalyzer.analyzeIdentifier(classObj.name, errors, indent+1);
            
            TsCodeAnalyzer.analyzeASTList(classObj.members, errors, indent+1);
        }

        static analyzeBlock(block: TypeScript.Block, errors: string[], indent: number = 0): void {
            TsCodeAnalyzer.debug("Analyzing Block", indent);
            TsCodeAnalyzer.analyzeASTList(block.statements, errors, indent+1);
        }

        static analyzeFuncDecl(func: TypeScript.FuncDecl, errors: string[], indent: number = 0): void {
            TsCodeAnalyzer.debug("Analyzing FuncDecl", indent);

            TsCodeAnalyzer.analyzeIdentifier(func.name, errors, indent+1);
            
            TsCodeAnalyzer.analyzeASTList(func.arguments, errors, indent + 1);

            var varDeclCount: number = 0;
            var sMembers = func.bod.members;
            var obj: TypeScript.AST = null;
            var lineCol = { line: -1, col: -1 };

            for (var i = 0; i < sMembers.length; i++) {
                obj = sMembers[i];
                if (obj instanceof TypeScript.VarDecl) {
                    varDeclCount++;
                    if (varDeclCount >= 2) {
                        var varDecl: TypeScript.VarDecl = (<TypeScript.VarDecl>obj);
                        TsCodeAnalyzer.parser.getSourceLineCol(lineCol, varDecl.minChar);
                        errors.push("Only one var declaration per block : (" + lineCol.line + "," + lineCol.col + ") : " + varDecl.printLabel());
                    }
                } else if (obj instanceof TypeScript.Block) {
                    var blc: TypeScript.Block = <TypeScript.Block>obj;
                    for (var j: number = 0; j < blc.statements.members.length; j++){
                        if (blc.statements.members[j] instanceof TypeScript.VarDecl) {
                            varDeclCount++;
                            if (varDeclCount >= 2) {
                                var varDecl: TypeScript.VarDecl = (<TypeScript.VarDecl>blc.statements.members[j]);
                                TsCodeAnalyzer.parser.getSourceLineCol(lineCol, varDecl.minChar);
                                errors.push("Only one var declaration per block : (" + lineCol.line + "," + lineCol.col + ") : " + varDecl.printLabel());
                            }
                            break;  // A block of declared vars
                        }
                    }
                }
            }

            TsCodeAnalyzer.analyzeASTList(func.bod, errors, indent+1);
        }

        static analyzeASTList(lst: TypeScript.ASTList, errors: string[], indent: number = 0): void {
            TsCodeAnalyzer.debug("Analyzing ASTList", indent);

            var sMembers = lst.members;
            var obj: any = null;
            var lineCol = { line: -1, col: -1 };
            
            for (var i = 0; i < sMembers.length; i++) {
                obj = sMembers[i];
                if (obj instanceof TypeScript.Identifier) {
                    TsCodeAnalyzer.analyzeIdentifier((<TypeScript.Identifier>obj), errors, indent+1);
                } else if (obj instanceof TypeScript.VarDecl) {
                    TsCodeAnalyzer.analyzeVarDecl((<TypeScript.VarDecl>obj), errors, indent+1);
                } else if (obj instanceof TypeScript.ClassDeclaration) {
                    var classObj = <TypeScript.ClassDeclaration>obj;
                    TsCodeAnalyzer.analyzeClassDeclaration(classObj, errors, indent+1);
                } else if (obj instanceof TypeScript.Block) {
                    TsCodeAnalyzer.analyzeBlock(<TypeScript.Block>obj, errors, indent+1);
                } else if (obj instanceof TypeScript.FuncDecl) {
                    TsCodeAnalyzer.analyzeFuncDecl(<TypeScript.FuncDecl>obj, errors, indent+1);
                } else {
                    TsCodeAnalyzer.debug("Unknown type [" + obj.printLabel() + "]", indent);
                }
            }
        }

        static analyzeScript(script: TypeScript.Script, errors: string[], indent:number=0): void {
            TsCodeAnalyzer.debug("Analyzing Script", indent);

            var varDeclCount: number = 0;
            var sMembers = script.bod.members;
            var obj: TypeScript.AST = null;
            var lineCol = { line: -1, col: -1 };
            
            for (var i = 0; i < sMembers.length; i++){
                obj = sMembers[i];
                if (obj instanceof TypeScript.VarDecl) {
                    varDeclCount++;
                    if (varDeclCount >= 2) {
                        var varDecl : TypeScript.VarDecl = (<TypeScript.VarDecl>obj);
                        TsCodeAnalyzer.parser.getSourceLineCol(lineCol, varDecl.minChar);
                        errors.push("Only one var declaration per block : (" + lineCol.line + "," + lineCol.col + ") : " + varDecl.printLabel());
                    }
                }
            }

            TsCodeAnalyzer.analyzeASTList(script.bod, errors, indent+1);

        }

    }
}

var code: string = "class myClass{"
                + " private ss : string;"
                + " a(arg1: string, arg2: number): string{ "
                + "             return 2;"
                + "  } "
                + "}\r\n";

var code2: string = " // Hello I'm a comment\r\n"
                + "class myClass2{"
                + " a(arg1: string, arg2: number): string{ "
                + "             return 2;"
                + "  } "
                + "}\r\n";

var code3: string = "var _a : boolean = false;\r\n";
var code4: string = "var b : string = 'tutu';\r\n";
var code5: string = "var a : boolean=false, b : string = 'tutu';";

//var codeSource = new TypeScript.StringSourceText(code3+code4+code2);
//QA.TsCodeAnalyzer.analyze(codeSource, "try.js");
// For nodejs ...
exports.QA = QA;
exports.TypeScript = TypeScript;

